package com.egg.basic.service.pay.factory.impl;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.egg.auth.enums.account.AccountTypeEnum;
import com.egg.auth.feign.AuthAccountFeignClient;
import com.egg.auth.model.response.web.UserAccountInfo;
import com.egg.basic.config.pay.PayWechatConfig;
import com.egg.basic.entity.pay.PayRecord;
import com.egg.basic.entity.pay.RefundRecord;
import com.egg.basic.enums.pay.ClientTypeEnum;
import com.egg.basic.enums.pay.PayTypeEnum;
import com.egg.basic.enums.pay.TradeStateEnum;
import com.egg.basic.model.rabbit.PayRabbitSender;
import com.egg.basic.rabbitmq.sender.PaySender;
import com.egg.basic.service.pay.factory.IPayFactory;
import com.egg.basic.service.pay.impl.PayRecordServiceImpl;
import com.egg.common.core.exception.CustomException;
import com.egg.common.core.model.response.ResponseData;
import com.egg.common.util.HttpServletUtil;
import com.egg.common.util.IpUtil;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.enums.TradeType;
import com.ijpay.core.kit.HttpKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.model.RefundModel;
import com.ijpay.wxpay.model.UnifiedOrderModel;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * 微信支付服务实现类
 */
@Slf4j
@Service
public class PayFactoryWechat implements IPayFactory, InitializingBean {


    @Autowired
    private PayRecordServiceImpl payRecordService;
    @Autowired(required = false)
    private PayWechatConfig payWechatConfig;
    @Autowired
    private AuthAccountFeignClient authAccountFeignClient;
    @Autowired
    private PaySender paySender;


    @Data
    static class H5SceneInfo {
        private H5 h5_info;

        @Data
        static class H5 {
            private String type;
            private String app_name;
            private String bundle_id;
            private String package_name;
        }
    }


    @Override
    public void afterPropertiesSet() {
        if (null != payWechatConfig) {
            container.put(PayTypeEnum.WECHAT.getCode(), this);
        }
    }

    private String appPay(PayRecord pr) {
        String ip = IpUtil.requestIp();
        if (ObjectUtil.isEmpty(ip)) {
            ip = "127.0.0.1";
        }
        Map<String, String> params = UnifiedOrderModel
                .builder()
                .appid(payWechatConfig.getAppId())
                .mch_id(payWechatConfig.getMchId())
                .nonce_str(WxPayKit.generateStr())
                .body(pr.getSubject())
                .out_trade_no(pr.getId().toString())
                .total_fee(pr.getAmount().toString())
                .spbill_create_ip(ip)
                .notify_url(payWechatConfig.getFullPayNotifyUrl())
                .trade_type(TradeType.APP.getTradeType())
                .build()
                .createSign(payWechatConfig.getPartnerKey(), SignType.HMACSHA256);
        String xmlResult = WxPayApi.pushOrder(false, params);
        Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
        String returnCode = result.get("return_code");
        String resultCode = result.get("result_code");
        String returnMsg = result.get("return_msg");
        if (!WxPayKit.codeIsOk(returnCode) || !WxPayKit.codeIsOk(resultCode)) {
            log.error("微信拉起APP支付失败 {}", returnMsg);
            throw new CustomException(returnMsg);
        }
        // 以下字段在 return_code 和 result_code 都为 SUCCESS 的时候有返回
        String prepayId = result.get("prepay_id");
        Map<String, String> packageParams = WxPayKit.appPrepayIdCreateSign(
                payWechatConfig.getAppId()
                , payWechatConfig.getMchId()
                , prepayId
                , payWechatConfig.getPartnerKey()
                , SignType.HMACSHA256
        );
        String jsonStr = JSON.toJSONString(packageParams);
        return jsonStr;
    }

    private String jsapiPay(PayRecord pr) {
        String ip = IpUtil.requestIp();
        if (ObjectUtil.isEmpty(ip)) {
            ip = "127.0.0.1";
        }
        ResponseData<UserAccountInfo> userAccountInfoRd = authAccountFeignClient.userAccountInfo(pr.getUserId(), AccountTypeEnum.APPLET.getCode());
        if (userAccountInfoRd.isError()) {
            throw new CustomException(userAccountInfoRd);
        }
        Map<String, String> params = UnifiedOrderModel
                .builder()
                .appid(payWechatConfig.getAppId())
                .mch_id(payWechatConfig.getMchId())
                .nonce_str(WxPayKit.generateStr())
                .body(pr.getSubject())
                .out_trade_no(pr.getId().toString())
                .total_fee(pr.getAmount().toString())
                .spbill_create_ip(ip)
                .notify_url(payWechatConfig.getFullPayNotifyUrl())
                .trade_type(TradeType.JSAPI.getTradeType())
                .openid(userAccountInfoRd.getData().getUsername())
                .build()
                .createSign(payWechatConfig.getPartnerKey(), SignType.HMACSHA256);
        String xmlResult = WxPayApi.pushOrder(params);

        log.debug(xmlResult);

        Map<String, String> resultMap = WxPayKit.xmlToMap(xmlResult);
        String returnCode = resultMap.get("return_code");
        String resultCode = resultMap.get("result_code");
        if (!WxPayKit.codeIsOk(returnCode) || !WxPayKit.codeIsOk(resultCode)) {
            String errCodeDes = resultMap.get("err_code_des");
            log.error("微信拉起JSAPI支付失败 {}", errCodeDes);
            throw new CustomException(errCodeDes);
        }
        // 以下字段在 return_code 和 result_code 都为 SUCCESS 的时候有返回
        String prepayId = resultMap.get("prepay_id");
        Map<String, String> packageParams = WxPayKit.prepayIdCreateSign(
                prepayId
                , payWechatConfig.getAppId()
                , payWechatConfig.getPartnerKey()
                , SignType.HMACSHA256
        );
        String jsonStr = JSON.toJSONString(packageParams);
        return jsonStr;
    }

    private String mwebPay(PayRecord pr) {
        String ip = IpUtil.requestIp();
        if (ObjectUtil.isEmpty(ip)) {
            ip = "127.0.0.1";
        }
        H5SceneInfo sceneInfo = new H5SceneInfo();
        H5SceneInfo.H5 h5_info = new H5SceneInfo.H5();
        h5_info.setType("Wap");
        sceneInfo.setH5_info(h5_info);
        Map<String, String> params = UnifiedOrderModel
                .builder()
                .appid(payWechatConfig.getAppId())
                .mch_id(payWechatConfig.getMchId())
                .nonce_str(WxPayKit.generateStr())
                .body(pr.getSubject())
                .out_trade_no(pr.getId().toString())
                .total_fee(pr.getAmount().toString())
                .spbill_create_ip(ip)
                .notify_url(payWechatConfig.getFullPayNotifyUrl())
                .trade_type(TradeType.MWEB.getTradeType())
                .scene_info(JSON.toJSONString(sceneInfo))
                .build()
                .createSign(payWechatConfig.getPartnerKey(), SignType.HMACSHA256);
        String xmlResult = WxPayApi.pushOrder(false, params);

        log.debug(xmlResult);

        Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
        String returnCode = result.get("return_code");
        String resultCode = result.get("result_code");
        String returnMsg = result.get("return_msg");
        if (!WxPayKit.codeIsOk(returnCode) || !WxPayKit.codeIsOk(resultCode)) {
            log.error("微信拉起MWEB支付失败 ", returnMsg);
            throw new CustomException(returnMsg);
        }
        // 以下字段在return_code 和result_code都为SUCCESS的时候有返回
        String prepayId = result.get("prepay_id");
        String webUrl = result.get("mweb_url");
        log.debug("prepay_id:" + prepayId + " mweb_url:" + webUrl);
        return webUrl;
    }

    /**
     * 微信支付
     *
     * @param pr
     * @return
     */
    @Override
    public ResponseData<String> add(PayRecord pr) {
        ClientTypeEnum clientTypeEnum = ClientTypeEnum.getByCode(pr.getClientType());
        switch (clientTypeEnum) {
            case APP:
                return ResponseData.success(appPay(pr));
            case JSAPI:
                return ResponseData.success(jsapiPay(pr));
            case MWEB:
                return ResponseData.success(mwebPay(pr));
            default:
                return ResponseData.error("客户端支付类型不支持");
        }
    }

    /**
     * 退款
     *
     * @param refund
     * @return
     */
    @Override
    public ResponseData<String> refund(RefundRecord refund) {
        Map<String, String> params = RefundModel.builder()
                .appid(payWechatConfig.getAppId())
                .mch_id(payWechatConfig.getMchId())
                .nonce_str(WxPayKit.generateStr())
                .transaction_id(refund.getTradeNo())
                .out_trade_no(refund.getPayRecordId().toString())
                .out_refund_no(refund.getId().toString())
                .total_fee(refund.getAmount().toString())
                .refund_fee(refund.getAmount().toString())
                .build()
                .createSign(payWechatConfig.getPartnerKey(), SignType.MD5);
        String refundStr = WxPayApi.orderRefund(false, params, payWechatConfig.getCertPath(), payWechatConfig.getMchId());

        Map<String, String> refundMap = WxPayKit.xmlToMap(refundStr);
        String returnCode = refundMap.get("return_code");
        String resultCode = refundMap.get("result_code");
        if (!WxPayKit.codeIsOk(returnCode) || !WxPayKit.codeIsOk(resultCode)) {
            return ResponseData.error(JSONObject.toJSONString(refundMap));
        }
        return ResponseData.success(JSONObject.toJSONString(refundMap));
    }

    /**
     * 微信支付回调
     *
     * @return
     */
    @Transactional(rollbackFor = Throwable.class)
    @Override
    public void payNotify() {
        HttpServletRequest request = HttpServletUtil.getHttpServletRequest();
        if (null == request) {
            return;
        }
        String xmlMsg = HttpKit.readData(request);
        Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);
        // 注意重复通知的情况，同一订单号可能收到多次通知，请注意一定先判断订单状态
        // 注意此处签名方式需与统一下单的签名类型一致
        if (!WxPayKit.verifyNotify(params, payWechatConfig.getPartnerKey(), SignType.HMACSHA256)) {
            return;
        }
        String outTradeNo = params.get("out_trade_no");
        Optional<PayRecord> payRecordOpt = payRecordService.baseFindById(outTradeNo);
        if (!payRecordOpt.isPresent()) {
            return;
        }
        PayRecord payRecord = payRecordOpt.get();
        if (!Objects.equals(payRecord.getTradeState(), TradeStateEnum.WAIT.getCode())) {
            //幂等校验
            return;
        }
        String returnCode = params.get("return_code");
        String resultCode = params.get("result_code");
        if (WxPayKit.codeIsOk(returnCode) && WxPayKit.codeIsOk(resultCode)) {
            payRecord.setTradeState(TradeStateEnum.SUCCESS.getCode());
        } else {
            payRecord.setTradeState(TradeStateEnum.FAIL.getCode());
            payRecord.setRemark(JSON.toJSONString(params));
        }
        String tradeNo = params.get("transaction_id");
        payRecord.setTradeNo(tradeNo);
        payRecordService.baseEdit(payRecord);

        //支付成功发送成功通知
        if (Objects.equals(payRecord.getTradeState(), TradeStateEnum.SUCCESS.getCode())) {
            PayRabbitSender sender = new PayRabbitSender();
            sender.setBusinessType(payRecord.getBusinessType());
            sender.setBusinessId(payRecord.getBusinessId());
            paySender.paySuccess(sender);
        }

    }


}
